#include <iostream>
#include <sys/stat.h>
#include <fcntl.h>
#include <list>
#include "Queue.h"
#include "RequestQueue.h"
#include "ResponseQueue.h"
#include "Messages.h"
#include "SIGINT_Handler.h"
#include "SignalHandler.h"
#include "TableFile.h"
#include "Constants.h"
#include "PersonsTable.h"

using namespace std;

#define SERVERPROCESS "Gestor"

void init() {
	// Crea el directorio base.
	mkdir(ROOTDIRECTORY, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);

	// Crea el key file para las queues.
	int fd = open(KEYFILEPATH, O_CREAT | O_WRONLY, 0644);
	close(fd);

	// Crea key files para los semaforos.
	fd = open(SEMKEYFILEPATH, O_CREAT | O_WRONLY, 0644);
	close(fd);
	fd = open(SEM2KEYFILEPATH, O_CREAT | O_WRONLY, 0644);
	close(fd);
	fd = open(SEM3KEYFILEPATH, O_CREAT | O_WRONLY, 0644);
	close(fd);
}

void handleUnknownRequest(QueueRecord<RequestMessage> &request, ResponseQueue &responseQueue) {
	ResponseMessage response;
	int result = 0;

	response.operation = request.data.operation;
    response.hasNext = false;
    response.success = false;
    response.record = request.data.record;

    cout << "Enviando respuesta por la cola respuestas..." << endl;
    result = responseQueue.Send(request.mtype, response);
    cout << "Respuesta enviada (result: " << result << ")" << endl;
}

void handleListRequest(PersonsTable &table, QueueRecord<RequestMessage> &request, ResponseQueue &responseQueue) {
	ResponseMessage response;
	list<TableRecord> records = table.getRecords();
    list<TableRecord>::iterator it = records.begin();
    int result = 0;

    if(it == records.end()) {
		response.operation = request.data.operation;
		response.success = false;
		response.record = request.data.record;
		response.hasNext = false;

		cout << "Enviando respuesta por la cola respuestas..." << endl;
		result = responseQueue.Send(request.mtype, response);
		cout << "Respuesta enviada (result: " << result << ")" << endl;
    } else {
		int i = 1;

		while (it != records.end()) {
			response.operation = request.data.operation;
			response.success = true;
			response.record = (*it);
			response.hasNext = (++it != records.end());

			cout << "Enviando respuesta " << i++ << " de " << records.size() << " por la cola respuestas..." << endl;
			result = responseQueue.Send(request.mtype, response);
			cout << "Respuesta enviada (result: " << result << ")" << endl;
		}
    }
}

void handleAddRequest(PersonsTable &table, QueueRecord<RequestMessage> &request, ResponseQueue &responseQueue) {
	ResponseMessage response;
	int result = 0;

	int addResult = table.addRecord(request.data.record);
    response.operation = request.data.operation;
    response.hasNext = false;
    response.record = request.data.record;
    response.record.id = addResult;
    response.success = (addResult > 0);

    cout << "Enviando respuesta por la cola respuestas..." << endl;
    result = responseQueue.Send(request.mtype, response);
    cout << "Respuesta enviada (result: " << result << ")" << endl;
}

void handleSearchRequest(PersonsTable &table, QueueRecord<RequestMessage> &request, ResponseQueue &responseQueue) {
	ResponseMessage response;
	int result = 0;

	TableRecord search = table.getRecord(request.data.record.id);
    response.operation = request.data.operation;
    response.hasNext = false;
    response.record = search;
    response.success = (search.id > 0);

    cout << "Enviando respuesta por la cola respuestas..." << endl;
    result = responseQueue.Send(request.mtype, response);
    cout << "Respuesta enviada (result: " << result << ")" << endl;
}

void handleDeleteRequest(PersonsTable &table, QueueRecord<RequestMessage> &request, ResponseQueue &responseQueue) {
	ResponseMessage response;
	int result = 0;

	TableRecord delResult = table.deleteRecord(request.data.record.id);
    response.operation = request.data.operation;
    response.hasNext = false;
    response.record = delResult;
    response.success = (delResult.id != -1);

    cout << "Enviando respuesta por la cola de respuestas..." << endl;
    result = responseQueue.Send(request.mtype, response);
    cout << "Respuesta enviada (result: " << result << ")" << endl;
}

string convertEnumToString(Operation operation) {
	switch (operation) {
		case LIST: return "list";
		case ADD: return "add";
		case SEARCH: return "search";
		case DELETE: return "delete";
		default: return "unknown";
	}
}

int main() {
	cout << "Iniciando nueva instancia del Gestor..." << endl;

	// Inicializo los archivos requeridos.
	init();

	SIGINT_Handler sigint_handler;
	SignalHandler::getInstance()->registerHandler(SIGINT, &sigint_handler);

	cout << "Cargando Base de Datos..." << endl;
	PersonsTable table;
	table.loadTableFromFile();
	if (!table.isTableLoaded()) {
		perror("No se pudo cargar la Base de Datos desde el medio fisico");
		return -1;
	}

	cout << "Creando cola de recepcion de pedidos..." << endl;
	RequestQueue requestQueue((char*)KEYFILEPATH, REQUESTCHAR);

	cout << "Creando cola de envio de respuestas..." << endl;
	ResponseQueue responseQueue((char*)KEYFILEPATH, RESPONSECHAR);

	QueueRecord<RequestMessage> request;
	int result = 0;

	do {
		cout << "Esperando request en la cola pedidos..." << endl;
		result = requestQueue.Receive(request);
		cout << "Request recibido (result: " << result << ")" << endl;

		if(result != -1) {
			cout << "- Request.mtype: " << request.mtype << endl;
			cout << "- Request.operation: " << convertEnumToString(request.data.operation) << endl;
			cout << "- Request.record.id: " << request.data.record.id << endl;
			cout << "- Request.record.name: " << request.data.record.name << endl;
			cout << "- Request.record.address: " << request.data.record.address << endl;
			cout << "- Request.record.phone: " << request.data.record.phone << endl;

			switch (request.data.operation) {
				case LIST:
					handleListRequest(table, request, responseQueue);
					break;
				case ADD:
					handleAddRequest(table, request, responseQueue);
					break;
				case SEARCH:
					handleSearchRequest(table, request, responseQueue);
					break;
				case DELETE:
					handleDeleteRequest(table, request, responseQueue);
					break;
				default:
					handleUnknownRequest(request, responseQueue);
			}

			cout << endl;
		}
	} while (!sigint_handler.quit());

	cout << endl << "Persistiendo Base de Datos..." << endl;
	table.persistTableToFile();

	cout << "Cerrando Gestor y liberando recursos..." << endl;
	requestQueue.destroy();
	responseQueue.destroy();
	SignalHandler::destroy();

	return 0;
}
